package md.frolov.bible.editors;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import md.frolov.bible.model.Chapter;
import md.frolov.bible.model.Verse;

import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.custom.VerifyKeyListener;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseListener;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;

public class BibleControl extends Composite implements ISelectionProvider{
	private Composite parent;
	private StyledText styledText;
	
	private List<StyleRange> styles = new ArrayList<StyleRange>(50);
	private List<Integer> offsets = new ArrayList<Integer>(50);
	private List<Integer> lengths = new ArrayList<Integer>(50);
	private List<String> refs = new ArrayList<String>(50);
	
	static int MARGIN = 2;
	private final Cursor handCursor = new Cursor(getDisplay(),SWT.CURSOR_HAND);
	private final Cursor beamCursor = new Cursor(getDisplay(),SWT.CURSOR_IBEAM);
	
	private final StyleRange verseLinkStyle;
	private final StyleRange strongLinkStyle;
	private final StyleRange chapterStyle;
	private final StyleRange highLightedStyle;
	
	private final static Pattern strongRegex = Pattern.compile("\\d+");
	
	private final static int INDENTATION = 10;
	
	private List<ISelectionChangedListener> selectionChangedListeners = new ArrayList<ISelectionChangedListener>();

	public BibleControl(Composite parent, int style) {
		super(parent, style);

		this.parent = parent;
		FillLayout layout = new FillLayout();
		this.setLayout(layout);
		styledText = new StyledText(this,SWT.WRAP | SWT.V_SCROLL | SWT.BORDER);
		styledText.setEditable(false);
		styledText.setEnabled(true);
		
		styledText.setIndent(INDENTATION);
		styledText.setLineSpacing(2);
		styledText.setCaret(null);
		
		verseLinkStyle = new StyleRange(0,0,new Color(getDisplay(),25,0,100),styledText.getBackground());
		verseLinkStyle.fontStyle = SWT.BOLD;
		verseLinkStyle.underline = false;
		
		highLightedStyle = new StyleRange(0,0,new Color(getDisplay(),100,30,30), styledText.getBackground());
		
		strongLinkStyle = new StyleRange(0,0,new Color(getDisplay(),170,200,150),styledText.getBackground());
		FontData fd = styledText.getFont().getFontData()[0];
		Font font = new Font(getDisplay(),fd.name,Math.round((float)(fd.height*0.6)), SWT.BOLD);
		strongLinkStyle.font = font;
		//TODO change metrics width to 0 and strong numbers will disappear
		
		chapterStyle = new StyleRange(0,0,styledText.getForeground(),styledText.getBackground());
		font = new Font(getDisplay(),fd.name,Math.round((float)(fd.height*2)), SWT.BOLD);
		chapterStyle.font = font;
		
		hookListeners();
	}
	
	private void hookListeners(){
		styledText.addMouseListener(new MouseListener(){

			@Override
			public void mouseDoubleClick(MouseEvent e) {
				System.out.println("Dblclicked: "+styledText.getSelectionText());
				fireTextSelectionChanged(styledText.getSelectionText());
			}

			@Override
			public void mouseDown(MouseEvent e) {
				int offset;
				try{
					offset = styledText.getOffsetAtLocation(new Point(e.x,e.y));
				} catch (IllegalArgumentException ex){
					return;
				}
				
				//moving cursor at the beginning of hyperlink
				styledText.setSelection(offsets.get(getRangeAtOffset(offset)).intValue());
				
				//firing selection Changed as SelectionProvider
				fireReferenceSelectionChanged(getRefAtOffset(offset));
				
				System.out.println("Reference: "+getRefAtOffset(offset));
			}

			@Override
			public void mouseUp(MouseEvent e) {

			}
		});
		
		styledText.addMouseMoveListener(new MouseMoveListener(){

			@Override
			public void mouseMove(MouseEvent e) {
				int offset;
				try{
					offset = styledText.getOffsetAtLocation(new Point(e.x,e.y));
				} catch (IllegalArgumentException ex){
					styledText.setCursor(beamCursor);
					return;
				}
				
				if(getRefAtOffset(offset)!=null){
					styledText.setCursor(handCursor);
				} else {
					styledText.setCursor(beamCursor);
				}
			}
			
		});

		styledText.addVerifyKeyListener(new VerifyKeyListener(){

			@Override
			public void verifyKey(VerifyEvent e) {
				if(e.keyCode == SWT.ARROW_DOWN ||
						e.keyCode == SWT.ARROW_LEFT ||
						e.keyCode == SWT.ARROW_RIGHT ||
						e.keyCode == SWT.ARROW_UP)
				{
					e.doit = false;
				}

				if(e.keyCode == SWT.ARROW_DOWN){
					try{
						Rectangle rect = styledText.getClientArea();
						int lineIx = styledText.getLineAtOffset(styledText.getOffsetAtLocation(new Point(INDENTATION,rect.height-1)));
						int lineCnt = styledText.getLineCount();
						if(lineIx+1>=lineCnt) {
							styledText.setSelection(styledText.getCharCount());
							return;
						}
						styledText.setSelection(styledText.getOffsetAtLine(lineIx+1));
					} catch (Exception ex){
						//do nothing
					}
				}
				
				if(e.keyCode == SWT.ARROW_UP){
					try{
						int lineIx = styledText.getLineAtOffset(styledText.getOffsetAtLocation(new Point(INDENTATION,0)));
						if(lineIx-1<0) {
							styledText.setSelection(0);
							return;
						}
						styledText.setSelection(styledText.getOffsetAtLine(lineIx-1));
					} catch(Exception ex){
						ex.printStackTrace();
					}
				}
			}
		});
	}
	
	private int getRangeAtOffset(int offset){
		for(int i=0;i<offsets.size();i++){
			if(offset>=offsets.get(i).intValue() && offset<offsets.get(i).intValue()+lengths.get(i).intValue()){
				return i;
			}
			if(offset<offsets.get(i).intValue()) break;
		}
		return -1;
	}
	
	private String getRefAtOffset(int offset){
		int ix = getRangeAtOffset(offset);
		if(ix==-1) return null;
		return refs.get(ix);
	}
	
	private void addStyle(StyleRange st, int start, int length, String ref){
		styles.add(st);
		offsets.add(new Integer(start));
		lengths.add(new Integer(length));
		refs.add(ref);
	}
	
	private void addStyle(StyleRange st, int start, int length){
		addStyle(st, start, length, null);
	}
	
	public void setContents(BibleEditorInput bei){
		initControls();
		
		Chapter chapter = bei.getChapter();
		int[] ixs = bei.getSelectedIndexes();
		int ix = 0;
		int aSelOffset = 0;
		int zSelOffset = 0;
		boolean selectedVerse = false;
		
		Verse[] verses = chapter.getVerses();
		StringBuffer sb = new StringBuffer();
		
		addStyle(chapterStyle, sb.length(), chapter.getName().length());
		sb.append(chapter.getName()).append(styledText.getLineDelimiter());
		
		for (int i=0;i<verses.length;i++) {
			if(ixs.length>0 && ix<ixs.length){
				if(String.valueOf(ixs[ix]).equals(verses[i].getLocalId())){
					if(ix==0) aSelOffset = sb.length();
					ix++;
					selectedVerse = true;
				}
			}

			addStyle(verseLinkStyle, sb.length(), verses[i].getLocalId().length(), verses[i].getUrl());
			sb.append(verses[i].getLocalId()).append(" ");

			int verseOffset = sb.length();
			Matcher m = strongRegex.matcher(verses[i].getContents());
			int lastOffset = 0;
			while(m.find(lastOffset)){
				if(selectedVerse && m.start()-lastOffset>1)
					addStyle(highLightedStyle, verseOffset+lastOffset, m.start()-lastOffset-1);
				lastOffset = m.end();
				addStyle(strongLinkStyle, m.start()+verseOffset, m.end()-m.start(), "strong."+m.group());
			}
			
			sb.append(verses[i].getContents()).append(styledText.getLineDelimiter());
			
			if(selectedVerse){
				selectedVerse = false;
				addStyle(highLightedStyle, verseOffset+lastOffset, verses[i].getContents().length()-lastOffset-1);
				if(ix==ixs.length) zSelOffset = sb.length();
			}
			
		}

		styledText.setText(sb.toString());
		if(aSelOffset>0){
			styledText.setSelection(zSelOffset);
		}
		styledText.setSelection(aSelOffset);
		
		StyleRange[] stRanges = new StyleRange[styles.size()];
		int[] ranges = new int[styles.size()*2];
		for (int i = 0; i < styles.size(); i++) {
			stRanges[i] = styles.get(i);
			ranges[i*2] = offsets.get(i).intValue();
			ranges[i*2+1] = lengths.get(i).intValue(); 
		}
		
		styledText.setStyleRanges(ranges, stRanges);
	}
	
	public void initControls(){
		styledText.setText("");
		refs.clear();
		offsets.clear();
		lengths.clear();
		styles.clear();
	}

	@Override
	public void addSelectionChangedListener(ISelectionChangedListener listener) {
		selectionChangedListeners.add(listener);
	}

	@Override
	public ISelection getSelection() {
		return new ITextSelection(){

			@Override
			public int getEndLine() {
				// TODO Auto-generated method stub
				return 0;
			}

			@Override
			public int getLength() {
				return styledText.getSelectionText().length();
			}

			@Override
			public int getOffset() {
				return styledText.getSelection().x;
			}

			@Override
			public int getStartLine() {
				// TODO Auto-generated method stub
				return 0;
			}

			@Override
			public String getText() {
				return styledText.getSelectionText();
			}

			@Override
			public boolean isEmpty() {
				return styledText.getSelectionText().isEmpty();
			}
			
		};
	}

	@Override
	public void removeSelectionChangedListener(ISelectionChangedListener listener) {
		selectionChangedListeners.remove(listener);
	}

	@Override
	public void setSelection(ISelection selection) {
		if(selection instanceof TextSelection){
			TextSelection ts = (TextSelection) selection;
			styledText.setSelection(ts.getOffset(),ts.getOffset()+ts.getLength());
		}
	}
	
	private void fireReferenceSelectionChanged(String reference){
		for (int i = 0; i < selectionChangedListeners.size(); i++) {
			selectionChangedListeners.get(i).selectionChanged(new SelectionChangedEvent(this,new BibleReferenceSelection(reference)));
		}
	}
	
	private void fireTextSelectionChanged(String text){
		for (int i = 0; i < selectionChangedListeners.size(); i++) {
			selectionChangedListeners.get(i).selectionChanged(new SelectionChangedEvent(this,new BibleTextSelection(text)));
		}
	}

	@Override
	public boolean setFocus() {
		return styledText.setFocus();
	}
	
}
